/* 
 * File:   buffer.c
 * Author: Tomás R. González Barroso  
 * Diseño de sistemas Operativos
 *
 * Created on 27 de junio de 2013, 8:34
 */

#include "comun.h"
#include "buffer.h"
#include "buffer_cache.h"
#include "system.h"
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <errno.h>     

extern int semid;
extern buffcach *Buffer_Cache;
//Señales
extern int sigreceived;
extern sigset_t sigset;

/*
//Manejadores de interrupciones
void sign_hadler_sigusr1 (int sign) {
    sigreceived = SIGUSR1;        
    printf("Señal SIGUSR1 recibida.\n");   
}
*/

//Liberamos Buffer, despertando procesos en espera SIGUSR1
void brelease(buffer *buff) {

    int* proc;
    int i=0, pid;
    
    if (buff != NULL) {
        //Liberamos procesos esperando por este buffer    
        proc = &(buff->sleep[0]);

        while ((proc[i] != -1) && (i< MAXSLEEP)) {
             pid = proc[i];
             //Sacamos de la lista en espera
             proc[i] = -1;
             // Enviamos señal para despertarlo
             kill(pid, SIGUSR1);
             i++;
        }
    }
    i = 0;
    //Liberamos los procesos pendientes de cualquier buffer
    proc = &( Buffer_Cache ->header.sleep[0]);    
    while ((proc[i] != -1) && (i< MAXSLEEP)) {
         pid = proc[i];
         //Sacamos de la lista en espera
         proc[i] = -1;
         // Enviamos señal para despertarlo
         kill(pid, SIGUSR1);
         i++;
    }    
}

//Libreramos buffer con exclusión mutua
void brelse(buffer *buff) {
    
    //Liberamos en exclusión mutua
    while (!semWait(semid));

    //Añadimos buffer lista libres
    bc_setfree(buff);

    //Desbloqueamos
    buff -> status_locked = FALSE;

    // Despertamos procesos esperando por cualquier buffer
    // y por este buffer
    brelease(buff);

    //Liberamos semaforo
    if (!semSignal(semid)) {
        perror("Error liberando semaforo. brelse");        
        exit(1);
    }
    
}

//Devolvemos un buffer para un inodo, bloque y fs
buffer *getblk(int fs, ino_t inode, int block) {
    
    buffer *buff = NULL;
    buffer *buff_aux = NULL;
    int salir = FALSE;
    int encontrado = FALSE;
     
   
    
    // Mientras no tengamos buffer 
    while (buff == NULL) {
        //Aseguramos exclusión mutua en el procedimiento
        //Activamos semaforo (rojo)
        while (!semWait(semid));       
        // Buscamos bloque % MAXHASH => hash
        int pos = Buffer_Cache->header.hash[block % MAXHASH];
        if (pos != -1) { //Buscamos buffer con el mismo inodo y bloque         
           do {
               buff_aux = &(Buffer_Cache->buffers[pos]);
               if ((buff_aux->inode == inode) && (buff_aux->block == block)) {
                   encontrado = TRUE;
                   salir = TRUE;
               }
               else 
                  pos = buff_aux->hashnext;
               if (pos == -1) 
                   salir = TRUE;
           }
           while (!salir);            
        }
        if (encontrado) {            
            printf("Buffer encontrado B(%d).\n",buff_aux->id);
            // Comprobamos si está bloqueado
            if  (buff_aux-> status_locked) {                
                printf("Buffer bloqueado, esperamos liberación\n");
                bwait(buff_aux); //Esperamos su liberacion
                continue;
            }

            // Asignamos datos del proceso al buffer
            //buff_aux->fd = s_getfd(inode);
            //buff_aux->pid = getpid();

            //Bloqueamos el buffer
            buff_aux-> status_locked = TRUE;
            
            //Sacamos de la lista libre
            buff = bc_getfree(buff_aux);            
        }
        else {
            // Extraemos un buffer libre
            printf("Buffer no encontrado, solicitando uno libre\n");          

            // Si no hay libres esperamos su liberacion
            if (Buffer_Cache->header.fl_start == -1) {
                printf("No disponibles, esperamos...\n");
                bwait(NULL); //NULL => Por cualquier buffer
                continue;
            }
            printf("Buffer(");           
            buff_aux = &(Buffer_Cache->buffers[Buffer_Cache->header.fl_start]);
            printf("%d).\n", buff_aux->id);
            // Si tiene marca escritura-retardada => enviamos señal y espera ativa
            if (buff_aux->status_delayed) {
               //Si es el buffer es "nuestro" guardamos sin enviar señal 
               if (buff_aux -> pid ==  getpid()) {
                    printf("Buffer mio, almacenando en disco.\n"); 
                    bwritedisc(buff_aux);
               }
               else {
                  printf("Escritura retardada B(%i).Enviando señal a PID(%i) y Esperando...\n",buff_aux->id,buff_aux->pid);              
                  // Enviamos señal para despertarlo               
                  kill(buff_aux->pid, SIGUSR2);
                  while (buff_aux->status_delayed) ;
                  printf("Escritura realizada. Cogiendo Buffer.\n");
               }
            }
            
            //Extraemos de la lista libre
            //printf("Extraer libre, ");            
            buff_aux = bc_getfree(NULL);
            // Extraemos buffer del Hash
            //printf(" Borrar Hash ");
            bc_drophash(buff_aux);
            //printf("BC_FREE_START => %d \n" ,Buffer_Cache->header.fl_start);
            //printf("BC_FREE_END => %d \n" ,Buffer_Cache->header.fl_end);
            buff_aux->tam = 0;
            buff_aux->inode = inode;
            buff_aux->block = block;
            // Bloqueamos y marcamos como invalido
            buff_aux -> status_locked = TRUE;
            buff_aux -> status_valid = FALSE;       
            //buff_aux->fd = s_getfd(inode); //0; 
            //buff_aux->pid = getpid();

            //Insertamos con el nuevo Hash          
            bc_pushhash(buff_aux);
                
            buff = buff_aux;                   
        }
        
    }
        
    // Liberamos semaforo (verde otros procesos)
    if (!semSignal(semid)) {
        perror("Error liberando semaforo. getblk");
        exit(1);
    }

    return buff;
}



//Buffer ocupado, nos quedamos a la espera de señal 
void bwait(buffer *buff) {

    int *sleep;
    int i, pid;
    
    if (buff != NULL)
        sleep = &(buff->sleep[0]);    
    else
        sleep = &(Buffer_Cache->header.sleep[0]);

    // buscamos ultimo proceso "dormido"
    i = 0;
    pid = getpid();
    while ((i < MAXSLEEP) && (sleep[i] != -1 ) && (sleep[i] != pid))
        i++;

    //Comprobamos si llegamos al maximo de procesos en espera
    if (i == MAXSLEEP) {
        perror("Sobrecarga en buffer de espera. bwait\n");
        exit(1);
    }
        
    //Almacenamos proceso en el vector
    sleep[i] = pid;

    // nos aseguramos librerar semaforo
    if (!semSignal(semid)) {
        perror("Error liberando semaforo. bwait\n");        
        exit(1);
    }

    
    // nos dormimos hasta recibir señal SIGUSR1
    printf("En espera....(PID:_%d)\n",pid);
    /*struct sigaction act;    
    act.sa_handler= sign_hadler_sigusr1;
    sigfillset(&act.sa_mask);
    act.sa_flags= 0;
    sigaction(SIGUSR1, &act, NULL);  
    sigaction(SIGUSR2, &act, NULL);  
    sigfillset(&sigset); 
    sigdelset(&sigset, SIGUSR2);
    sigdelset(&sigset, SIGUSR1);*/
    while (sigreceived != SIGUSR1);// sigsuspend(&sigset);
    sigreceived = 0;
    
}

//Marcamos buffer con escritura_retardada
void bwrite(buffer *buff) {
    
    //Marcamos buffer con escritura_retardada
    buff->status_delayed = TRUE;
   
}

//Mandamos a escribir el bloque a disco
//si somos el proceso propietario del primer buffer libre
void basyncwrite() {
    buffer *tmp_buffer;
    int mipid;
    
    if (Buffer_Cache->header.fl_start != -1) {      
        mipid = getpid();
        //Comprobamos si el primer buffer libre es del proceso
        //para guardar a disco
        tmp_buffer = &(Buffer_Cache->buffers[Buffer_Cache->header.fl_start]);
        printf("Buff_PID(%i) , MiPID(%i).\n",tmp_buffer-> pid,getpid());
        if (tmp_buffer-> pid == mipid) {
           printf("Almacenando Buffer a disco.\n"); 
           bwritedisc(tmp_buffer);
        }      
        
    }
    
}

//Escribimos el buffer a disco
void bwritedisc(buffer *tmp_buffer) {
    int err = -1;
    
    // Nos posicionamos en la posicion correcta        
    lseek( tmp_buffer ->fd, tmp_buffer ->block * BLKSIZE, SEEK_SET);
    // Escribimos 
    err = write(tmp_buffer -> fd,tmp_buffer ->data,tmp_buffer ->tam);
    if (err == -1) { 
        s_print();
        printf("write (%d,%s,%ld) \n",tdfu.files[tmp_buffer->fd].fdreal,tmp_buffer ->data,tmp_buffer ->tam);
        perror("Error grabando datos al fichero.");
        exit(1);
    }
    else 
        printf("(%d) bytes escritos a disco correctamente. Buff(%i)\n",err,tmp_buffer ->id); 
    tmp_buffer -> pid = -1;
    tmp_buffer -> fd = -1;    
    tmp_buffer->status_delayed = FALSE;
    
    
   
}

//Leemos desde disco el buffer
void breaddisc(buffer *tmp_buffer,int fd) {
    int err = -1;
    
    // Nos posicionamos en la posicion correcta        
    err = lseek(fd, tmp_buffer ->block * BLKSIZE, SEEK_SET);
    
    // Leemos
    err = read(fd,tmp_buffer ->data,BLKSIZE);    
    if (err == -1) {
        perror("Error lectura fichero.\n");
        exit(1);
    }
    else {
        tmp_buffer ->tam = err;
        printf("(%d) bytes leidos correctamente de disco. Buff(%i)\n",err,tmp_buffer ->id); 
    }
    tmp_buffer->status_valid = TRUE;
    
}


//Leemos de disco el tamaño de un buffer
//Solicitaremos para ello buffer libre -> getblk
buffer *bread(int fs, ino_t inode, int block,int fd){
    buffer *tmp_buffer;
    
    tmp_buffer =getblk(fs,inode,block);
    
    if (!tmp_buffer ->status_valid ) {
        //Leemos de disco los datos correctos
        breaddisc(tmp_buffer,fd);                                
    } 
    return(tmp_buffer);
}

//Visualizamos datos del buffer indicado por pantalla
void bprint(buffer *buff) {
    int tmp,i;
    
    printf("Id: %d\n", buff->id);    
    printf("PID: %d\n", buff->pid);
    printf("Fd: %d\n", buff->fd);
    printf("Bloque: %d\n", buff->block);
    printf("inode: %d\n", (int)buff->inode);
    printf("Bloqueado(%d) Valido(%d) Escritura(%d)\n",buff->status_locked,buff->status_valid, buff->status_delayed);
    printf("Tamaño: %ld\n", buff->tam);
    printf("Datos: [%s]\n", buff->data);
    printf("freeprev -> %d\n", buff->freeprev);
    printf("freenext -> %d\n", buff->freenext);
    printf("hashprev -> %d\n", buff->hashprev);
    printf("hashnext -> %d\n", buff->hashnext);   
     //Visualizamos Lista procesos en espera (Sleep)    
    printf("Lista Procesos esperando este Buffer => ");
    for (i=0;i < MAXSLEEP;i++) {    
        tmp = buff -> sleep[i];
        if (tmp != -1)
           printf("PID(%i),",tmp);        
    }
    printf("\n");
}

//Guaramos a disco todos los buffers del proceso y fd indicado 
//con escritura retardada (exclusión mutua)
void bwrite_fclose(int pid,int fd) {    
    
    int i;
    buffer *tmp_buffer;
    
    //Activamos semaforo (rojo)
    while (!semWait(semid)); 
    
    for (i = 0; i < MAXBUFF; i++) {
        tmp_buffer = &(Buffer_Cache->buffers[i]);        
        if ((tmp_buffer->pid == pid) && (tmp_buffer->fd == fd) && (tmp_buffer->status_delayed))
            bwritedisc(tmp_buffer);            
    }
    
    // Liberamos semaforo (verde otros procesos)
     if (!semSignal(semid)) {
        perror("Error liberando semaforo. bwrite_fclose");
        exit(1);
    }

}


int bmap(off_t io) {
    int block;
    
    block = io / BLKSIZE;
    
    return block;
}